#pragma once

#include <iostream>
#include <array>
#include <cassert>

namespace NonClassParam
{
    /**
     * 1、类模板参数
     */
    template <typename T = int, std::size_t MaxSize = 100>
    class StackNonType
    {
    public:
        void push(const T &elem);
        void pop();
        const T &top() const;

        bool empty() const
        {
            return elemNum == 0;
        }

        std::size_t size() const
        {
            return elemNum;
        }

    private:
        std::array<T, MaxSize> elems;
        std::size_t elemNum{0};
    };

    template <typename T, std::size_t MaxSize>
    inline void StackNonType<T, MaxSize>::push(const T &elem)
    {
        assert(elemNum < MaxSize);
        elems[elemNum++] = elem;
    }

    template <typename T, std::size_t MaxSize>
    inline void StackNonType<T, MaxSize>::pop()
    {
        assert(!empty());
        --elemNum;
    }

    template <typename T, std::size_t MaxSize>
    const T &StackNonType<T, MaxSize>::top() const
    {

        assert(!empty());
        return elems[elemNum - 1];
    }

    /**
     * 2、函数模板参数
     */
    template <int Val, typename T>
    T addValue(T x)
    {
        return x + Val;
    }
    // 让其自动推导的写法（since C++17）
    template <auto Val, typename T = decltype(Val)>
    T addValue_(T x)
    {
        return Val;
    }

    /**
     * 3、模板参数的限制：
     * 
     * 0、调用模板时，传入的非类型模板参数必须是编译期确定的（指针不是编译期确定的，const char[] 则是）
     * 
     * 1、定义模板时的参数限制：
     * 模板非类型参数的类型必须是编译时已知的常量表达式（constexpr）。例如，整数、枚举值、指针、引用等!!!
     *      浮点数和类型对象不允许作为非类型模板参数!!!
     * 
     * 2、使用模板时的参数限制：
     * 向指针或引用传递模板参数时，对象不能是字符串字面值、临时对象或数据成员和其他子对象!!!
     *      在 C++17 前，每个 C++ 版本都放宽了这些限制，所以还有其他的限制:
     * • C++11 中，对象必须具有外部链接。
     * • C++14 中，对象必须具有外部或内部链接。
     * • C++17 中，对象必须具有外部或内部链接或是局部变量。
     * （内部链接：当前文件内的全局变量，其他文件不可访问）
     * （外部链接：带external关键字的全局变量，其他文件也可以访问）
     * （局部变量：{}内的变量）
     * 3、避免无效的表达式
     *      非类型模板参数可以是编译时表达式，但是表达式需要用()包含，以便编译器确定 > 在哪里结束
     */

    // 报错：
    // template<double Val>
    // double func(){}

    // 报错：
    // template<std::string Val>
    // std::string func(){}
    template<int T,bool B>
    class C{
    };

    /**
     * 4、模板参数类型 auto
     * 传递的参数必须时编译时确定的，因为模板的展开是在编译期
     * 4.1.通过使用占位符类型 auto，可以将 参数 定义为尚未指定类型的值，可以是任何允许为 非类型模板参数 的类型
     * 4.2.C++14 后，也可以在这里使用 auto 作为返回类型，让编译器确定返回类型
     * 4.3.template<decltype(auto) N> class C; decltype(auto)会将参数N作为引用传递。比如: int i; C<(i)> c; // 传入的N是int&   
     */
    template<decltype(auto) N>
    class DecltypeC{
    public:
        DecltypeC(){
            std::cout << "DecltypeC init: "<< N <<std::endl;
            // N = 20;
        }
    };
};